﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;                // file operations
using System.Drawing;           // make pictures
using System.Drawing.Imaging;   // image formats
using GeneralDFS.BusinessLogical.Nodes;
using GeneralDFS.DataEntity.Nodes;

namespace GeneralDFS.BusinessLogical.Chord
{
    public class MapImage
    {
        /// <summary>
        /// Given a starting node for a valid Chord ring, gather the list of all nodes in the ring,
        /// and get the hopcount map from each node to each other node in the system.
        /// </summary>
        /// <param name="seedNode">The starting node in a valid Chord ring.</param>
        /// <param name="nodeList">The list of Chord nodes in the ring.  This list is already sorted.</param>
        /// <param name="hopcountMap">An N x N matrix of hopcounts where x=y=nodeList.Count.</param>
        public static void GenerateMap(NameNodeInfo seedNode, out List<NameNodeInfo> nodeList, out int[,] hopcountMap)
        {
            // first get the list of all of the nodes in the system
            nodeList = GetNodeList(seedNode);

            // set up the hopcountMap to be an N x N matrix where N is the number of nodes in the ring.
            hopcountMap = new int[nodeList.Count, nodeList.Count];

            // now, for each node in the nodelist, measure FindSuccessor() hopcounts to each other node in
            // the nodelist, and store them in the hopcount map.
            //
            // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE
            //
            //  Obviously, this can (and should) be multithreaded for any reasonably large system.  I've
            //  only made this sample program single-threaded for readability.  Translating from a single-
            //  threaded implementation to a multithreaded implementation of the style of the developer's
            //  choice should be considered a reasonable enough task for anyone experimenting more than casually
            //  with NChord (beyond a few thousand nodes).
            //

            // sort the nodelist by nodeId (ChordNode implements IComparable and compares on the Node ID value).
            nodeList.Sort();

            // now, go to each node, and have it call FindSuccessor on that nodeId, and hold on to the hopCount
            // by storing it in the hopCountmap at the appropriate position

            int y = 0;  // the "row" offset in hopcountMap
            foreach (NameNodeInfo sourceNode in nodeList)
            {
                int x = 0;  // the "column" offset in hopcountMap

                // get hopcount from the sourceNode to each destinationNode
                foreach (NameNodeInfo destinationNode in nodeList)
                {
                    // CallFindSuccessor connects to sourceNode and calls FindSuccessor for the destination node's
                    // ID.  3 retries (in case of communication failure, etc.) should be more than sufficient.
                    // since we are the initiating node, pass in a value of 0 as the starting hopcount, and store
                    // the resultant hopcount in the appropriate place in the hopcountMap.
                    NameNode.CallFindSuccessor(sourceNode, destinationNode.ID, 3, 0, out hopcountMap[y, x]);

                    x++;    // move to the next column in the row
                }
                y++;    // move to the next row in the map
            }

            // there you go, you have a hopcount map
        }

        /// <summary>
        /// Get the list of all of the nodes in a Chord ring by starting at seedNode
        /// and following the successor chain around the ring.  There are better ways
        /// to do this, though churn can almost always screw things up in generating a
        /// map of what would ideally represent flawlessly the state of the entire 
        /// distributed system at a single given point in time.  Realizing that the world
        /// is not a perfect place can provide great peace at times - you just do the
        /// best you can and accept imperfection.  That's what we do here.
        /// </summary>
        /// <param name="seedNode">The node to start collecting the list of nodes in the ring from.</param>
        /// <returns>A list of nodes in the Chord ring.</returns>
        private static List<NameNodeInfo> GetNodeList(NameNodeInfo seedNode)
        {
            List<NameNodeInfo> nodeList = new List<NameNodeInfo>();
            nodeList.Add(seedNode);     // don't forget to add the seedNode!

            // the current node (starting with the seed node's successor)
            // as we iterate through the ring.
            NameNodeInfo currNode = NameNode.GetSuccessor(seedNode);

            int i = 0;  // the number of iterations we have made (so we don't loop infinitely)
            while (seedNode.ID != currNode.ID && i < 6000)
            {
                // get my successor and add it to the nodeList.
                // eventually, my successor will be the seedNode
                // or i will hit the maximum node limit, at which
                // point i just stop
                currNode = NameNode.GetSuccessor(currNode);
                nodeList.Add(currNode);
                i++;
            }

            return nodeList;
        }

        /// <summary>
        /// Output the hop count map to a JPG file.
        /// </summary>
        /// <param name="hopcountMap">The hopcount map to dump to JPG.</param>
        /// <param name="nodeList">The list of nodes used to generate the map.</param>
        /// <param name="outputFilename">The path and filename to output the CSV file to.</param>
        public static void GenerateImage(int[,] hopcountMap, List<NameNodeInfo> nodeList, string outputFileName)
        {
            // create an N x N bitmap into which the map will be written
            Bitmap mapImage = new Bitmap(nodeList.Count, nodeList.Count);

            for (int y = 0; y < nodeList.Count; y++)
            {
                for (int x = 0; x < nodeList.Count; x++)
                {
                    Color pixelColor = Color.White; // default color in case of error

                    // if we have a color for the given hopcount
                    if (hopcountMap[y, x] <= ColorMap.Length)
                    {
                        // set the color based on the hop count in the map
                        pixelColor = ColorMap[hopcountMap[y, x]];
                    }

                    // set the appropriate pixel to the color representing the hopcount
                    mapImage.SetPixel(x, y, pixelColor);
                }
            }

            mapImage.Save(outputFileName, ImageFormat.Jpeg);
        }

        /// <summary>
        /// The map of colors to use in generating the image. Each item
        /// corresponds to a hop count value - colors may be added or changed here.
        /// </summary>
        private static Color[] ColorMap = 
        {
            Color.Black,                    /* 0  */
            Color.LightGray,                /* 1  */
            Color.Gray,                     /* 2  */
            Color.LightBlue,                /* 3  */
            Color.Blue,                     /* 4  */
            Color.Navy,                     /* 5  */
            Color.LightGreen,               /* 6  */
            Color.Green,                    /* 7  */
            Color.LightYellow,              /* 8  */
            Color.Yellow,                   /* 9  */
            Color.Red,                      /* 10 */
            Color.Maroon,                   /* 11 */
            Color.Purple,                   /* 12 */
            Color.Plum                      /* 13 */
        };
    }
}
